/**
 * 
 */
package de.muyat.kablosuzizci.istemci;

import java.util.List;

import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

/**
 * http://muyat.googlecode.com/files/20121207_211839.jpg adresindeki formule gore
 * pozisyon hesabi yapar.
 * 
 * @author Dzhoshkun Ismail Shakir
 *
 */
public class PozisyonSensoru extends Thread implements SensorEventListener {
	private boolean pozisyonIlklendiMi;
	// thread ile ilgili
	private Thread guncelleyici;
	private static boolean kosuyor;
	private static final int GUNCELLEME_PERIYODU = 100; // ms
	// ortak kullanilan data
	private static float[] 	akselerometreDegerleri = new float[3],
							manyetikAlanDegerleri = new float[3],
							gercekIvmeDegerleri = new float[3],
							yercekimi = new float[]{0, 0, 0};
	private static Koordinat koordinat;
	private static Oryantasyon oryantasyon;
	private static RotasyonMatrisi rotasyonMatrisi;
	// high-pass filtresi (yercekimini elimine etmek icin) gereken degerler
	private static final float ALPHA = 0.8f;
	// sensor ile ilgili
	private static Context baglam;
	private SensorManager sensorMenajeri;
	private Sensor akselerometre, oryantasyonSensoru;
	private static final int 	IVME_SENSOR_IDSI = Sensor.TYPE_ACCELEROMETER,
								ORYANTASYON_SENSOR_IDSI = Sensor.TYPE_MAGNETIC_FIELD;
	
	/**
	 * 
	 * @param baglam sensor menajerinin calismasi icin bu gerekli
	 * @throws NullPointerException {@code baglam} bos ise
	 */
	public PozisyonSensoru(Context baglam) throws NullPointerException {
		if (baglam == null)
			throw new NullPointerException();
		PozisyonSensoru.baglam = baglam;
		pozisyonIlklendiMi = false;
		kosuyor = false;
		sensorMenajeri = (SensorManager) PozisyonSensoru.baglam.getSystemService(Context.SENSOR_SERVICE);
		akselerometre = sensorMenajeri.getDefaultSensor(IVME_SENSOR_IDSI);
		sensorMenajeri.registerListener(this, akselerometre, SensorManager.SENSOR_DELAY_FASTEST);
		oryantasyonSensoru = sensorMenajeri.getDefaultSensor(ORYANTASYON_SENSOR_IDSI);
		sensorMenajeri.registerListener(this, oryantasyonSensoru, SensorManager.SENSOR_DELAY_FASTEST);
	}
	
	/**
	 * 
	 * @return kosturulan telefonda, bu sinifin gereksinim duydugu sensorler varsa {@code true}, yoksa {@code false}
	 */
	public boolean sensorVarMi() {
		List<Sensor> sensorListesi = sensorMenajeri.getSensorList(IVME_SENSOR_IDSI);
		if (sensorListesi==null)
			return false;
		if (sensorListesi.isEmpty())
			return false;
		sensorListesi = sensorMenajeri.getSensorList(ORYANTASYON_SENSOR_IDSI);
		if (sensorListesi==null)
			return false;
		if (sensorListesi.isEmpty())
			return false;
		return true;
	}
	
	/**
	 * Pozisyonu ilkler.
	 * @param koordinat
	 * @param oryantasyon
	 * @throws IllegalStateException daha once metod cagrildiysa
	 */
	public void setPozisyon(Koordinat koordinat, Oryantasyon oryantasyon) throws IllegalStateException {
		if (pozisyonIlklendiMi)
			throw new IllegalStateException();
		
		PozisyonSensoru.koordinat = koordinat;
		PozisyonSensoru.oryantasyon = oryantasyon;
		pozisyonIlklendiMi = true;
	}

	/**
	 * Pozisyonu guncelleyen threadi kosturur.
	 * @throws IllegalStateException {@link #setPozisyon(Koordinat, Oryantasyon)} metodu cagrilip ilkleme yapilmadiysa, veya
	 * kosma baslatildiysa
	 */
	public void basla() throws IllegalStateException {
		if (!pozisyonIlklendiMi)
			throw new IllegalStateException();
		
		if (kosuyorMu())
			throw new IllegalStateException();
		
		guncelleyici = new Thread(this);
		guncelleyici.start();
	}
	
	@Override
	public void run() {
		try {
			kosuyor = true;
			float periyot = GUNCELLEME_PERIYODU/1000.0f;
			Hiz hiz = new Hiz(0, 0, 0);
			Ivme ivme;
			float[] rotasyonMatrisDizini = new float[9];
			float[] egimMatrisDizini = new float[9]; // simdilik dummy
			float[] acilar = new float[3];
			final float GURULTU_SINIR_DEGERI = 2.0f;
			while (kosuyorMu()) {
				SensorManager.getRotationMatrix(rotasyonMatrisDizini, egimMatrisDizini, akselerometreDegerleri, manyetikAlanDegerleri);
				acilar = SensorManager.getOrientation(rotasyonMatrisDizini, acilar);
				synchronized(PozisyonSensoru.class) {
					oryantasyon = new Oryantasyon(acilar[0], acilar[1], acilar[2]);
					rotasyonMatrisi = new RotasyonMatrisi(rotasyonMatrisDizini);
					
					ivme = new Ivme(gercekIvmeDegerleri[0], gercekIvmeDegerleri[1], gercekIvmeDegerleri[2]);
					rotasyonMatrisi.dondur(ivme);
				}
//				TODO ivme.gurultuFiltrele(GURULTU_SINIR_DEGERI);
				hiz.guncelle(ivme, periyot);
				synchronized (PozisyonSensoru.class) {
					koordinat.guncelle(hiz, periyot);
				}
				
				sleep(GUNCELLEME_PERIYODU);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Pozisyon guncelleyen threadi durdurur.
	 * @throws IllegalStateException kosma baslatilmadiysa
	 */
	public void dur() throws IllegalStateException {
		if (!kosuyorMu())
			throw new IllegalStateException();
		synchronized(PozisyonSensoru.class) {
			kosuyor = false;
		}
		try {
			guncelleyici.join();
			sensorMenajeri.unregisterListener(this);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 
	 * @return pozisyonu guncelleyen thread kosuyorsa {@code true}, kosmuyorsa {@code false}
	 */
	private boolean kosuyorMu() {
		synchronized (PozisyonSensoru.class) { 
			return kosuyor;
		}
	}
	
	/**
	 * 
	 * @return guncel pozisyon
	 * @throws IllegalStateException {@link #setPozisyon(Koordinat, Oryantasyon)} metodu cagrilip ilkleme yapilmadiysa
	 */
	public Pozisyon guncelPozisyonuVer() throws IllegalStateException {
		if (!kosuyorMu())
			throw new IllegalStateException();
		synchronized(PozisyonSensoru.class) {
			return new Pozisyon(koordinat, oryantasyon);
		}
	}
	
	/**
	 * 
	 * @return guncel ivme
	 * @throws IllegalStateException {@link #setPozisyon(Koordinat, Oryantasyon)} metodu cagrilip ilkleme yapilmadiysa
	 */
	public Ivme guncelIvmeyiVer() throws IllegalStateException {
		if (!kosuyorMu())
			throw new IllegalStateException();
		synchronized (PozisyonSensoru.class) {
			return new Ivme(akselerometreDegerleri[0], akselerometreDegerleri[1], akselerometreDegerleri[2]);
		}
	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int arg1) {
		// nop
	}

	@Override
	public void onSensorChanged(SensorEvent olay) {
		// alpha is calculated as t / (t + dT)
        // with t, the low-pass filter's time-constant
        // and dT, the event delivery rate
		switch (olay.sensor.getType())
		{
		case IVME_SENSOR_IDSI:

	        synchronized(PozisyonSensoru.class) {
	        	System.arraycopy(olay.values, 0, akselerometreDegerleri, 0, 3);
	        	
	        	// http://developer.android.com/reference/android/hardware/SensorEvent.html#values'den esinlenilmistir
	        	yercekimi[0] = ALPHA * yercekimi[0] + (1 - ALPHA) * olay.values[0];
	        	yercekimi[1] = ALPHA * yercekimi[1] + (1 - ALPHA) * olay.values[1];
	        	yercekimi[2] = ALPHA * yercekimi[2] + (1 - ALPHA) * olay.values[2];
	
	            gercekIvmeDegerleri[0] = olay.values[0] - yercekimi[0];
	            gercekIvmeDegerleri[1] = olay.values[1] - yercekimi[1];
	            gercekIvmeDegerleri[2] = olay.values[2] - yercekimi[2];
			}
	        break;
		case ORYANTASYON_SENSOR_IDSI:
			synchronized(PozisyonSensoru.class) {
	        	System.arraycopy(olay.values, 0, manyetikAlanDegerleri, 0, 3);
			}
			break;
		}
		
	}
}
